import {alawFromPCM} from 'g711';
import { getConfigByKey } from '@/api/system/config';
export const Recorder = {
  params: new Params(),
  getUserMedia:function(constraints) {
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      // 最新标准的方法，支持现代浏览器
      return navigator.mediaDevices.getUserMedia(constraints);
    } else {
      // 旧版浏览器兼容处理
      let getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
      if (getUserMedia) {
        return new Promise(function(resolve, reject) {
          getUserMedia.call(navigator, constraints, resolve, reject);
        });
      } else {
        return Promise.reject(new Error('获取浏览器麦克风权限失败，请开启浏览器麦克风权限！'));
      }
    }
  },
  /**
   * 开启实时喊话
   * @param url 语音推流地址
   * @param errorFunCell 异常回调函数
   * @param sampleRate 采样率，不传默认8000，喇叭采样率44100
   * @param autoClose 自动关闭回调方法
   * @returns {Promise}
   */
  start: function(url, errorFunCell, sampleRate, autoClose) { //开启录音
    return new Promise((resolve, reject) => {
      this.stop();
      const self = this; // 将this赋值给变量self
      this.getUserMedia({
        audio: true
      }).then(function(stream){
        self.params.stream = stream;
        self.params.url = url;
        if (errorFunCell) {
          self.params.errorFunCell = errorFunCell;
        }
        if (sampleRate) {
          self.params.sampleRate = sampleRate;
        }
        if(autoClose) {
          self.params.autoClose = autoClose;
          self.params.autoCloseTime = 5;
          getConfigByKey('intercomAutoCloseHold').then((resp) => {
            if(resp.data?.configValue) {
              self.params.autoCloseTime = Number(resp.data?.configValue);
            }
          });
        }
        const sp = url.split('/');
        self.params.token = sp[sp.length - 1];
        self.getWebSocket(url);
        resolve();
      }).catch(function(error) {
        let title = this.dealErrorTitle(error);
        reject(new Error(title));
      });
    });

  },
  dealErrorTitle: function (error) {
    console.log('获取麦克风权限失败-' + error);
    let title = '获取麦克风权限异常';
    if (error) {
      title = error.message;
      if (error.name.indexOf("NotAllowedError") > -1) {
        if (error.message.indexOf("system") > -1) {
          title = "获取系统麦克风权限失败，请开启系统麦克风权限！";
        } else {
          title = "获取浏览器麦克风权限失败，请开启浏览器麦克风权限！";
        }
      }
    }
    return title
  },

  init: function() { //初始化组件
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    this.params.context = new AudioContext();//{ sampleRate: this.params.sampleRate }
    this.params.littleEdian = (function() {
      let buffer = new ArrayBuffer(2);
      new DataView(buffer).setInt16(0, 256, true);
      return new Int16Array(buffer)[0] === 256;
    })();
    this.params.input = this.params.context.createMediaStreamSource(this.params.stream);
    this.params.recorder = this.params.context.createScriptProcessor(4096, 1, 1);
    this.params.input.connect(this.params.recorder);
    this.params.recorder.connect(this.params.context.destination);
    this.params.recorder.onaudioprocess = (e) => {
      this.params.timeStamp = e.timeStamp;
      const input = e.inputBuffer.getChannelData(0); //解析输入的音频
      const volume = Math.round(Math.max.apply(Math, input) * 100);
      if(this.params.autoCloseTime > 0 && this.params.sendTime === 0) {
        this.params.sendTime = e.timeStamp;
      }
      if(volume > 5) {
        console.log('分贝===>', volume);
        this.params.sendTime = e.timeStamp;
        let pcm = this.transformIntoPCM(input,null);
        this.toG711a_new(pcm);
      } else {
        if(this.params.autoCloseTime > 0) {
          this.autoClose();
          return false;
        }
        if(this.params.timeStamp - this.params.sendTime > this.params.keepAlive) {
          this.params.sendTime = e.timeStamp;
          this.toRtp(new Uint8Array('AudioKeepAlive'.split('')));
        }
      }
    }
  },
  autoClose: function() { //自动关闭
    if(this.params.timeStamp - this.params.sendTime > this.params.autoCloseTime * 1000) {
      console.log('============自动关闭==============');
      this.params.autoClose();
      this.stop();
    }
  },
  transformIntoPCM: function(lData, rData) {
    let lBuffer = new Float32Array(lData),
      rBuffer = new Float32Array(rData);
    const inputSampleRate = this.params.context.sampleRate;//48000
    const outputSampleRate = this.params.sampleRate;//8000
    console.log("采样率===>",outputSampleRate)
    let data = this.compress({
      left: lBuffer,
      right: rBuffer,
    }, inputSampleRate, outputSampleRate);
  return this.encodePCM(data, this.params.sampleBits, this.params.littleEdian)
  },
  encodePCM: function(bytes, sampleBits, littleEdian) {
    let offset = 0,
      dataLength = bytes.length * (sampleBits / 8),
      buffer = new ArrayBuffer(dataLength),
      data = new DataView(buffer);

    // 写入采样数据
    if (sampleBits === 8) {
      for (let i = 0; i < bytes.length; i++, offset++) {
        // 范围[-1, 1]
        let s = Math.max(-1, Math.min(1, bytes[i]));
        // 8位采样位划分成2^8=256份，它的范围是0-255;
        // 对于8位的话，负数*128，正数*127，然后整体向上平移128(+128)，即可得到[0,255]范围的数据。
        let val = s < 0 ? s * 128 : s * 127;
        val = +val + 128;
        data.setInt8(offset, val);
      }
    } else {
      for (let i = 0; i < bytes.length; i++, offset += 2) {
        let s = Math.max(-1, Math.min(1, bytes[i]));
        // 16位的划分的是2^16=65536份，范围是-32768到32767
        // 因为我们收集的数据范围在[-1,1]，那么你想转换成16位的话，只需要对负数*32768,对正数*32767,即可得到范围在[-32768,32767]的数据。
        data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, littleEdian);
      }
    }
    return data;
  },
  compress: function(data, inputSampleRate, outputSampleRate) {
    // 压缩，根据采样率进行压缩
    let rate = inputSampleRate / outputSampleRate,
      compression = Math.max(rate, 1),
      lData = data.left,
      rData = data.right,
      length = Math.floor(( lData.length + rData.length ) / rate),
      result = new Float32Array(length),
      index = 0,
      j = 0;

    // 循环间隔 compression 位取一位数据
    while (index < length) {
      let temp = Math.floor(j)

      result[index] = lData[temp];
      index++;

      if (rData.length) {
        /*
         * 双声道处理
         * e.inputBuffer.getChannelData(0)得到了左声道4096个样本数据，1是右声道的数据，
         * 此处需要组和成LRLRLR这种格式，才能正常播放，所以要处理下
         */
        result[index] = rData[temp];
        index++;
      }

      j += compression;
    }
    // 返回压缩后的一维数据
    return result;
  },
  getWebSocket: function(url) {
    if (this.params.socket && String(this.params.socket.readyState) === '1') {
      this.params.socket.close();
      this.params.socket = null;
    }
    console.log("socket连接开始====>",url);
    this.params.socket = new WebSocket(url);
    this.params.socket.onopen = () => {
      console.log("socket连接成功");
      this.init();
    };
    // 监听socket错误信息
    this.params.socket.onerror = () => {
      console.log("socket连接失败");
      this.params.errorFunCell("wss地址连接失败,请稍后重试");
    };
    // 监听socket关闭监听
    this.params.socket.onclose = () => {
      console.log("socket连接关闭");
      if (!this.params.isStop) {
        this.params.errorFunCell("wss地址连接断开,请稍后重试");
      }
    };

  },
  toG711a_new: function(pcmData) { //Uint8转G711a
    // 假设有一个名为 dataView 的 DataView 对象
    const length = pcmData.byteLength / 2; // 一个 16 位整数占用 2 个字节
    const int16Array = new Int16Array(length);
    for (let i = 0; i < length; i++) {
      const offset = i * 2; // 一个 16 位整数占用 2 个字节
      const value = pcmData.getInt16(offset, true); // true 表示使用小字节序
      int16Array[i] = value;
    }
    const g711a = alawFromPCM(int16Array);
    this.params.tempPCM.push(g711a);
    this.toRtp(g711a);
  },
  toRtp: function(g711) { //G711打包rtp
    const rtp = new Uint8Array(g711.length + 12); // 创建一个RTP数据包数组，长度是G711数组长度加上12个字节的RTP头部
    this.params.rtpSequenceNumber ++;
    this.params.rtpTimeStamp += g711.length;
    rtp[0] = (2 << 6) | (0 << 5) | (0 << 4) | (0 << 0); // 设置RTP头部的第一个字节，包括版本号（2）、填充标志（0）、扩展标志（0）和CSRC计数（0）
    rtp[1] = (0 << 7) | (8 << 0); // 设置RTP头部的第二个字节，包括标记位（0）和有效载荷类型（8，表示G711 A-law）
    rtp[2] = (this.params.rtpSequenceNumber >> 8) & 255; // 设置RTP头部的第三和第四个字节，表示序列号（每次递增1）
    rtp[3] = (this.params.rtpSequenceNumber >> 0) & 255;
    rtp[4] = (this.params.rtpTimeStamp >> 24) & 255; // 设置RTP头部的第五到第八个字节，表示时间戳（每次递增采样点数）
    rtp[5] = (this.params.rtpTimeStamp >> 16) & 255;
    rtp[6] = (this.params.rtpTimeStamp >> 8) & 255;
    rtp[7] = (this.params.rtpTimeStamp >> 0) & 255;
    rtp[8] = (this.params.token >> 24) & 255; // 设置RTP头部的第九到第十二个字节，表示同步源标识符（固定不变）
    rtp[9] = (this.params.token >> 16) & 255;
    rtp[10] = (this.params.token >> 8) & 255;
    rtp[11] = (this.params.token >> 0) & 255;
    for (let i = 0; i < g711.length; i++) { // 将G711数据复制到RTP数据包数组的后面
      rtp[i + 12] = g711[i];
    }
    console.log("send->success--",this.params.rtpTimeStamp);
    this.params.socket.send(rtp);
  },
  stop: function() { //停止录音
    this.params.isStop = true;
    if(this.params.input) {
      this.params.input.disconnect();
    }
    if(this.params.recorder) {
      this.params.recorder.disconnect();
    }
    if(this.params.context) {
      this.params.context.close();
    }
    if(this.params.stream) {
      this.params.stream.getTracks().forEach((track) => {
        track.stop();
      });
    }
    //this.downloadPCM("test");
    if (this.params.socket && String(this.params.socket.readyState) === '1') {
      this.params.socket.close();
    }
    this.params = new Params();
  },
  downloadPCM: function (name) {
    if (this.params.tempPCM.length) {
      // 计算合并后的总长度
      let totalLength = 0;
      for (const array of this.params.tempPCM) {
        totalLength += array.length;
      }
      // 创建一个新的Uint8Array来存储合并后的数据
      const mergedArray = new Uint8Array(totalLength);

      // 使用set()方法将每个Uint8Array的数据复制到合并后的Uint8Array中
      let offset = 0;
      for (const array of this.params.tempPCM) {
        mergedArray.set(array, offset);
        offset += array.length;
      }
      try {
        console.log("tempPCM",mergedArray)
        let oA = document.createElement('a');

        oA.href = window.URL.createObjectURL(new Blob([mergedArray],{ type: 'audio/g711a' }));
        oA.download = name + '.g711a';
        oA.click();
      } catch(e) {

      }
    }


  }
}
function Params() {
  return {
    url: null, //长链接地址
    errorFunCell:() => {
      console.log('解决sonar问题');
    }, //自动关闭回调
    token: null, //token
    sampleRate: 8000, //采样率
    sampleBits:16,
    context: null, //音频控制
    stream: null, //媒体流
    input: null, //输入音频
    recorder: null, //音频
    timeStamp: 0, //时间戳
    sendTime: 0, //发送时间
    keepAlive: 3000, //保活时间(毫秒)
    rtpSequenceNumber: 0, //rtp序列号
    rtpTimeStamp: 0, //rtp时间戳
    socket: null, //socket链接
    littleEdian: true,
    tempPCM:[],
    isStop:false,
  };
}
